﻿namespace JoinBox.RemoteAccess;


using System;
using System.IO;
using System.Reflection;


public class JAppDomain : IDisposable
{
    /// <summary>
    /// 新域
    /// </summary>
    AppDomain? _newAppDomain;

    /// <summary>
    /// 新域的通讯代理类(通过它和其他程序域沟通)
    /// </summary>
    public RemoteLoader JRemoteLoader;

    /// <summary>
    /// 程序域的创建和释放
    /// </summary>
    /// <param name="newAppDomainName">新程序域名</param>
    /// <param name="assemblyPlugs">子目录（相对形式）在AppDomainSetup中加入外部程序集的所在目录,多个目录用分号间隔</param>
    public JAppDomain(string newAppDomainName, string? assemblyPlugs = null)
    {
        // 如果是文件就转为路径
        var ap = assemblyPlugs;
        if (!string.IsNullOrEmpty(ap))
            ap = Path.GetDirectoryName(ap);

        var path = RemoteLoaderTool.GetAssemblyPath(true); // 插件目录
        path = Path.GetDirectoryName(path);
        if (!string.IsNullOrEmpty(ap) && ap != path)
            ap += ";" + path;

        // 创建App域
        var ads = new AppDomainSetup
        {
            ApplicationName = newAppDomainName,
            // 应用程序根目录
            ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
            // 子目录（相对形式）在AppDomainSetup中加入外部程序集的所在目录,多个目录用分号间隔
            PrivateBinPath  = ap ??= path,
        };
        // 设置缓存目录
        ads.CachePath = ads.ApplicationBase;   // 获取或设置指示影像复制是打开还是关闭
        ads.ShadowCopyFiles = "true";          // 获取或设置目录的名称,这些目录包含要影像复制的程序集
        ads.ShadowCopyDirectories = ads.ApplicationBase;

        ads.DisallowBindingRedirects = false;
        ads.DisallowCodeDownload = true;

        ads.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

#if true2
        // 从安全策略证据新建程序域(应该是这句导致通讯类无法获取文件)
        var adevidence = AppDomain.CurrentDomain.Evidence;

        // 创建第二个应用程序域。
        AppDomainFactory.JAppDomain = AppDomain.CreateDomain(newAppDomainName, adevidence, ads);
#endif
        _newAppDomain = AppDomain.CreateDomain(newAppDomainName, null, ads);


        // 遍历程序集,获取指定的程序集 RemoteLoader
        string? assemblyName = null;
        foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
        {
            if (ass == typeof(RemoteLoader).Assembly)
            {
                assemblyName = ass.FullName;
                break;
            }
        }
        if (assemblyName is null)
            throw new ArgumentNullException(nameof(RemoteLoader) + "程序域不存在");

        try
        {
#if true2
            var masterAppDomain = AppDomain.CurrentDomain;
            // 如果通讯类是主域的话表示新域没用,加载进来的东西都在主域.
            // 做了个寂寞,释放了空域
            JRemoteLoader = masterAppDomain.CreateInstanceAndUnwrap(assemblyName, typeof(RemoteLoader).FullName) as RemoteLoader;
#else
            // 在新域创建通讯类,会引发错误: System.ArgumentException:“无法跨 AppDomain 传递 GCHandle。”
            // 因为使用了cad的dll,而它的dll用了非托管对象
            JRemoteLoader = (RemoteLoader)_newAppDomain.CreateInstanceAndUnwrap(assemblyName, typeof(RemoteLoader).FullName);
#endif
        }
        catch (Exception)
        {
            Dispose();
            throw new ArgumentNullException(
                "需要将*通讯类.dll*扔到acad.exe,而c盘权限太高了," +
                "所以直接复制一份acad.exe所有文件到你的主工程Debug目录," +
                "调试都改到这个目录上面的acad.exe");
        }

        // 不用下面的字符串形式,否则改个名就报错了...
        // try
        // {
        //    JRemoteLoader = _newAppDomain.CreateInstance(
        //           RemoteLoaderTool.RemoteAccessNamespace,
        //           RemoteLoaderTool.RemoteAccessNamespace + ".RemoteLoader")// 类名
        //           .Unwrap() as RemoteLoader;
        // }
        // catch (Exception e)// 报错是否改了 RemoteLoader名称
        // {
        //    throw e;
        // }
    }

    #region IDisposable接口相关函数
    public bool IsDisposed { get; private set; } = false;

    /// <summary>
    /// 手动调用释放
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 析构函数调用释放
    /// </summary>
    ~JAppDomain()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        // 不重复释放,并设置已经释放
        if (IsDisposed) return;
        IsDisposed = true;

        // 系统卸载出错,而手动卸载没出错,因为 rrl = RemoteLoaderLong 在
        if (_newAppDomain is not null)       
            AppDomain.Unload(_newAppDomain);
        
        _newAppDomain = null;
    }
    #endregion
}
